package nl.unimaas.micc.umsnooker.control.ai;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;

import java.util.logging.Level;
import java.util.logging.Logger;

import com.jme.input.ChaseCamera;
import com.jme.input.thirdperson.ThirdPersonMouseLook;
import com.jme.math.FastMath;
import com.jme.math.Vector3f;

import nl.unimaas.micc.umsnooker.control.ControllingUnit;
import nl.unimaas.micc.umsnooker.control.ai.evaluator.SimpleScoreEvaluator;
import nl.unimaas.micc.umsnooker.control.ai.generator.RandomCueInteractionGenerator;
import nl.unimaas.micc.umsnooker.control.ui.controller.SimulationControl;
import nl.unimaas.micc.umsnooker.domain.BilliardBall;
import nl.unimaas.micc.umsnooker.gameflow.GamePhase;
import nl.unimaas.micc.umsnooker.gameflow.GameState;
import nl.unimaas.micc.umsnooker.gameflow.phases.AimingPhase;
import nl.unimaas.micc.umsnooker.gameflow.phases.FrameInitPhase;
import nl.unimaas.micc.umsnooker.gameflow.phases.PositioningPhase;
import nl.unimaas.micc.umsnooker.gameflow.phases.SimulationPhase;
import nl.unimaas.micc.umsnooker.presentation.TablePresentation;
import nl.unimaas.micc.umsnooker.simulation.EventHandler;
import nl.unimaas.micc.umsnooker.simulation.InTimeSimulation;
import nl.unimaas.micc.umsnooker.simulation.Simulation;
import nl.unimaas.micc.umsnooker.simulation.event.BallCollision;
import nl.unimaas.micc.umsnooker.simulation.event.CueInteraction;
import nl.unimaas.micc.umsnooker.simulation.event.Event;
import nl.unimaas.micc.umsnooker.simulation.event.PocketingEvent;

public class RandomAIControl implements ControllingUnit
{
	private static final Logger logger = Logger.getLogger(RandomAIControl.class.getName());

	private TablePresentation presentation;
	private GameState state;
	private float accuracy = 0.0005f;
	private int visualizationDepth = 2;
	private CueInteraction cueStrike;


	public RandomAIControl(TablePresentation presentation, GameState state)
	{
		this.presentation = presentation;
		this.state = state;
	}
	
	public void handlePhase(GamePhase phase)
	{
		HashMap<String, Object> props = new HashMap<String, Object>();

		props.put(ThirdPersonMouseLook.PROP_MAXROLLOUT, "3");
		props.put(ThirdPersonMouseLook.PROP_MINROLLOUT, "1");
		props.put(ThirdPersonMouseLook.PROP_MINASCENT, "" + 45
				* FastMath.DEG_TO_RAD);
		props.put(ThirdPersonMouseLook.PROP_MAXASCENT, "" + 45
				* FastMath.DEG_TO_RAD);
		props.put(ChaseCamera.PROP_INITIALSPHERECOORDS, new Vector3f(3, 0,
				30 * FastMath.DEG_TO_RAD));
		props.put(ChaseCamera.PROP_WORLDUPVECTOR, new Vector3f(0, 0, 1));

		props.put(ChaseCamera.PROP_TARGETOFFSET, new Vector3f(0, 0, 0));

		ChaseCamera chaser = new ChaseCamera(presentation.getCamera(), presentation.getTableNode(), props);
		chaser.setMinDistance(state.getTable().getLength());
		chaser.setMaxDistance(state.getTable().getLength());

		presentation.setCameraControl(chaser);
		
		if (phase instanceof FrameInitPhase)
		{
			handleInit((FrameInitPhase) phase);
		}
		else if (phase instanceof PositioningPhase)
		{
			handlePositioning((PositioningPhase) phase);
		}
		else if (phase instanceof AimingPhase)
		{
			handleAiming((AimingPhase) phase);
		}
		else if (phase instanceof SimulationPhase)
		{
			handleSimulation((SimulationPhase) phase);
		}
	}

	private void handleInit(FrameInitPhase phase)
	{
		presentation.initBalls();
	}

	private void handleSimulation(SimulationPhase phase)
	{
		presentation.setGameControl(new SimulationControl(presentation, phase, state));
	}

	private void handlePositioning(PositioningPhase phase)
	{
		phase.setPosition(state.getTable().getBrownSpot().x + state.getTable().getDRadius() / 2f, state.getTable().getBrownSpot().y - state.getTable().getDRadius() / 2f);
		phase.finish();
	}

	private void handleAiming(AimingPhase phase)
	{	
		RandomCueInteractionGenerator randomCIGenerator = new RandomCueInteractionGenerator();
		SimpleScoreEvaluator simpleScoreEvaluator = new SimpleScoreEvaluator();
		
		
		long before = System.currentTimeMillis();
		int count = 0;
		
		double score;
		do
		{
			count++;
			cueStrike = (CueInteraction) randomCIGenerator.generate(state);
			score = simpleScoreEvaluator.evaluate(cueStrike, state);
		} while (score < 1);
		
		float time = (System.currentTimeMillis() - before)/1000f;
		logger.log(Level.FINE,"Generated " + count + " shots in "+ time + "s : "+ count/time + " shots/s");

		
		phase.setAngDest(cueStrike.getAngDest());
		phase.setAngElev(cueStrike.getAngElev());
		phase.setTransX(cueStrike.getTransX());
		phase.setTransY(cueStrike.getTransY());
		phase.setVelocity(cueStrike.getVelocity());
		
		presentation.showShotVisualization(true);

		showAimLine();
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		presentation.showShotVisualization(false);

		phase.finish();
	}

	private void showAimLine(){
		
		BilliardBall simCueBall = (BilliardBall) state.getCueBall();
		
		Event cueShot = new CueInteraction(0, simCueBall, cueStrike.getAngDest(), cueStrike.getAngElev(), cueStrike.getTransX(), cueStrike.getTransY(), cueStrike.getVelocity());

		Set<Event> initEvents = new HashSet<Event>();
		initEvents.add(cueShot);

		final Simulation sim = new InTimeSimulation(
				new HashSet<BilliardBall>(state.getBalls()),
				state.getTable());
		sim.init(initEvents);

		class AimingHandler implements EventHandler
		{
			public int collisionCount = 0;
			public BilliardBall monitoredBall;
			public ArrayList<ArrayList<Vector3f>> linePointsList;
			public ArrayList<Vector3f> points;

			@Override
			public void handle(Event e)
			{
				if (e instanceof BallCollision)
				{
					Set<BilliardBall> balls = e.getBallKeys();
					if (balls.contains(monitoredBall))
					{
						sim.getBall(monitoredBall);
						addPointFromMonitoredBall();
						BilliardBall newMon = monitoredBall;
						for (BilliardBall b : balls)
						{
							if (!b.equals(monitoredBall))
							{
								newMon = (BilliardBall) b.clone();
							}
						}
						monitoredBall=newMon;

						linePointsList.add(points);
						presentation.updateShotVisualization(linePointsList);
						points = new ArrayList<Vector3f>();
						addPointFromMonitoredBall();
						
						collisionCount++;
						if (collisionCount > visualizationDepth)
						{
							sim.pause();
						}
					}
				}
				if (e instanceof PocketingEvent)
				{
					Vector3f newPoint = ((PocketingEvent)e).getPocket().getPosition().clone();
					addPoint(newPoint);
					linePointsList.add(points);
					sim.pause();
				}

			}
			
			public void addPointFromMonitoredBall()
			{
				addPoint(monitoredBall.getPosition().clone());
				
			}
			
			public void addPoint(Vector3f point)
			{
				point.z = monitoredBall.getRadius();
				point.x = point.x	- (0.5f * state.getTable().getWidth());
				point.y = point.y - (0.5f * state.getTable().getLength());
				points.add(point);
			}
		}

		AimingHandler aimingHandler = new AimingHandler();
		aimingHandler.monitoredBall = (BilliardBall) simCueBall.clone();
		aimingHandler.linePointsList = new ArrayList<ArrayList<Vector3f>>();
		aimingHandler.points = new ArrayList<Vector3f>();
		aimingHandler.addPointFromMonitoredBall();
		sim.addEventHandler(aimingHandler);

		int step = 1;
		while (!sim.isPaused() && sim.hasNextEvent())
		{
			sim.getBall(aimingHandler.monitoredBall);
			aimingHandler.addPointFromMonitoredBall();
			sim.advanceTime(accuracy*step);
			step++;
		}
		aimingHandler.linePointsList.add(aimingHandler.points);
		presentation.updateShotVisualization(aimingHandler.linePointsList);

	}
}
